package com.naiterui.ehp.bs.esign.api.wukong.service.impl;

import com.naiterui.common.redis.RedisUtil;
import com.naiterui.ehp.bp.constants.CommonConstant;
import com.naiterui.ehp.bp.constants.EsignServiceProviderEnum;
import com.naiterui.ehp.bp.support.exception.BusinessException;
import com.naiterui.ehp.bp.utils.date.DateUtil;
import com.naiterui.ehp.bs.esign.api.ISignService;
import com.naiterui.ehp.bs.esign.api.vo.SignAccountVO;
import com.naiterui.ehp.bs.esign.api.vo.SignCallbackVO;
import com.naiterui.ehp.bs.esign.api.vo.SignFileParamVO;
import com.naiterui.ehp.bs.esign.api.vo.SignUserInfoVO;
import com.naiterui.ehp.bs.esign.api.vo.UserInfoParamVO;
import com.naiterui.ehp.bs.esign.api.wukong.helper.AccountHelper;
import com.naiterui.ehp.bs.esign.api.wukong.helper.MobileCodeHelper;
import com.naiterui.ehp.bs.esign.api.wukong.helper.OpenApiHelper;
import com.naiterui.ehp.bs.esign.api.wukong.helper.SealHelper;
import com.naiterui.ehp.bs.esign.api.wukong.helper.SignHelper;
import com.naiterui.ehp.bs.esign.api.wukong.helper.VerifyPDFHelper;
import com.naiterui.ehp.bs.esign.api.wukong.vo.SignFaceResultVO;
import com.naiterui.ehp.bs.esign.exception.ExceptionCodes;
import com.naiterui.ehp.bs.esign.service.IEsignService;
import com.naiterui.ehp.bs.esign.vo.esign.PreSignResultVO;
import com.timevale.esign.sdk.tech.bean.AbstractSignPdfBean;
import com.timevale.esign.sdk.tech.bean.AccountProfile;
import com.timevale.esign.sdk.tech.bean.PersonBean;
import com.timevale.esign.sdk.tech.bean.PosBean;
import com.timevale.esign.sdk.tech.bean.SignBean;
import com.timevale.esign.sdk.tech.bean.SignPDFFileBean;
import com.timevale.esign.sdk.tech.bean.SignPDFStreamBean;
import com.timevale.esign.sdk.tech.bean.SigningAccount;
import com.timevale.esign.sdk.tech.bean.SigningInfo;
import com.timevale.esign.sdk.tech.bean.UpdatePersonBean;
import com.timevale.esign.sdk.tech.bean.WillnessInfo;
import com.timevale.esign.sdk.tech.bean.result.FileDigestSignResult;
import com.timevale.esign.sdk.tech.bean.result.VerifyPdfResult;
import com.timevale.esign.sdk.tech.bean.seal.PersonTemplateType;
import com.timevale.esign.sdk.tech.bean.seal.SealColor;
import com.timevale.esign.sdk.tech.impl.constants.DeleteParamType;
import com.timevale.esign.sdk.tech.impl.constants.LegalAreaType;
import com.timevale.esign.sdk.tech.impl.constants.LicenseQueryType;
import com.timevale.esign.sdk.tech.impl.constants.SignType;
import com.timevale.esign.sdk.tech.v3.client.ServiceClient;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;

import java.util.ArrayList;
import java.util.List;
import java.util.UUID;

/**
 * @Description 签名服务实现
 * @Author gongbaoqiang
 * @Date 2019/12/20 5:50 PM
 */
@Slf4j
public class SignServiceImpl implements ISignService {

    private AccountHelper accountHelper;

    private SealHelper sealHelper;

    private MobileCodeHelper mobileCodeHelper;

    private SignHelper signHelper;

    private VerifyPDFHelper verifyPDFHelper;

    private OpenApiHelper openApiHelper;

    public SignServiceImpl(ServiceClient serviceClient) {
        accountHelper = new AccountHelper(serviceClient);
        sealHelper = new SealHelper(serviceClient);
        mobileCodeHelper = new MobileCodeHelper(serviceClient);
        signHelper = new SignHelper(serviceClient);
        verifyPDFHelper = new VerifyPDFHelper(serviceClient);
        openApiHelper = new OpenApiHelper();
    }

    /**
     * 三要素实名认证流程id
     */
    private final static String REALNAME_CODE_FLOWID = CommonConstant.REDIS_PRE_ESIGN + "phone_";
    /**
     * 实名认证短信验证码发送缓存
     */
    public static final String REALNAME_MESSAGE_CODE_PHONE = CommonConstant.REDIS_PRE_ESIGN + "realname_message_code_phone_";

    /**
     * 实名认证短信验证码可重复发送间隔
     */
    public static final int REALNAME_MESSAGE_CODE_EXIPRED_SECOND = 60;


    @Override
    public EsignServiceProviderEnum getCurrentServiceProvider() {
        return EsignServiceProviderEnum.ESIGN_WUKONG_SDK;
    }

    @Override
    public SignUserInfoVO registAccount(UserInfoParamVO userInfoVO) throws BusinessException {
        log.info("注册签名账号：personBean={}", userInfoVO.toString());
        PersonBean personBean = new PersonBean();
        personBean.setName(userInfoVO.getName());
        personBean.setMobile(userInfoVO.getPhone());
        personBean.setIdNo(userInfoVO.getIdNo());
        personBean.setPersonArea(this.getLegalAreaType(userInfoVO.getCerType()));
        personBean.setTitle(userInfoVO.getTitle());
        personBean.setOrgan(userInfoVO.getOrgan());

        String accountId = null;

        try {
            AccountProfile accountProfile = accountHelper.getAccountInfoByIdNo(userInfoVO.getIdNo(), LicenseQueryType.MAINLAND);
            if (accountProfile != null) {
                userInfoVO.setOpenId(accountProfile.getAccountUid());
                this.updateAccount(userInfoVO);
                accountId = accountProfile.getAccountUid();
            }else{
                accountId = accountHelper.addAccount(personBean);
            }
        } catch (BusinessException e) {
            log.error("电子签名账户注册失败", e);
            throw new BusinessException(ExceptionCodes.ESIGN_ACCOUNT_ADD_FAIL);
        }
//        String sealData = null;
//        if (userInfoVO.isSealFlag()) {
//            sealData = createSeal(accountId, userInfoVO.getName());
//        }

        return SignUserInfoVO.builder()
                             .openId(accountId)
                             .callbackType(SignUserInfoVO.TYPE_CALLBACK_INTERFACE)
                             .build();
    }

    /**
     * 自定义证件类型与e签宝证件类型映射关系
     *
     * @param type
     *
     * @return
     */
    private LegalAreaType getLegalAreaType(int type) {
        switch (type) {
            case 1:
                return LegalAreaType.MAINLAND;
            case 2:
                return LegalAreaType.HONGKONG;
            case 3:
                return LegalAreaType.MACAO;
            case 4:
                return LegalAreaType.TAIWAN;
            case 5:
                return LegalAreaType.FOREIGN;
            case 6:
                return LegalAreaType.PASSPORT;
            case 7:
                return LegalAreaType.SOLDIER_IDNO;
            case 8:
                return LegalAreaType.SOCIAL_SECURITY_CARD;
            case 9:
                return LegalAreaType.ARMED_POLICE_ID;
            case 10:
                return LegalAreaType.RESIDENCE_BOOKLET;
            case 11:
                return LegalAreaType.TEMPORARY_IDNO;
            case 12:
                return LegalAreaType.FOREIGNER_PERMANENT_RESIDENCE_PERMIT;
            default:
                return LegalAreaType.MAINLAND;
        }
    }

    @Override
    public SignUserInfoVO updateAccount(UserInfoParamVO userInfoVO) throws BusinessException {
        log.info("更新签名账号信息：userInfoVO={}", userInfoVO.toString());
        //配置需要更新的数据
        UpdatePersonBean personBean = new UpdatePersonBean();
        if(StringUtils.isNotBlank(userInfoVO.getName())){
            personBean.setName(userInfoVO.getName());
        }
        if(StringUtils.isNotBlank(userInfoVO.getPhone())){
            personBean.setMobile(userInfoVO.getPhone());
        }
        if(StringUtils.isNotBlank(userInfoVO.getOrgan())){
            personBean.setOrgan(userInfoVO.getOrgan());
        }
        if(StringUtils.isNotBlank(userInfoVO.getTitle())){
            personBean.setTitle(userInfoVO.getTitle());
        }

        //配置需要置空的数据
        List<DeleteParamType> deleteParamType = new ArrayList<>();
        try {
            accountHelper.updateAcct(userInfoVO.getOpenId(), personBean, deleteParamType);
        } catch (BusinessException e) {
            throw new BusinessException(ExceptionCodes.ESIGN_ACCOUNT_ADD_FAIL);
        }
        String sealData = null;
        if (userInfoVO.isSealFlag()) {
            sealData = this.createSeal(userInfoVO.getOpenId(), userInfoVO.getName(),null);
        }

        return SignUserInfoVO.builder()
                             .openId(userInfoVO.getOpenId())
                             .sealData(sealData)
                             .callbackType(SignUserInfoVO.TYPE_CALLBACK_INTERFACE)
                             .build();
    }

    @Override
    public String getRealnameCode(String name, String phone, String idCard) throws BusinessException {
        //校验是否是1分钟内重复提交实名申请
        if (RedisUtil.keyOps().existsKey(REALNAME_MESSAGE_CODE_PHONE + phone)) {
            throw new BusinessException(ExceptionCodes.ESIGN_REALNAME_MESSAGE_CODE_REPEAT);
        }
        //业务流程id
        String flowId = openApiHelper.realnameByCode(name, phone, idCard);
        RedisUtil.valueOps().set(REALNAME_CODE_FLOWID + phone, flowId);
        RedisUtil.keyOps().expire(REALNAME_MESSAGE_CODE_PHONE + phone, REALNAME_MESSAGE_CODE_EXIPRED_SECOND);
        return flowId;
    }

    @Override
    public boolean checkRealnameCode(String phone, String code) throws BusinessException {
        String flowId = RedisUtil.valueOps().getString(REALNAME_CODE_FLOWID + phone);
        if (flowId == null) {
            throw new BusinessException(ExceptionCodes.ESIGN_REALNAME_AUTH_FAIL);
        }

        return openApiHelper.checkRealnameCode(flowId, code);
    }

    /**
     * 此功能可合并到注册账户接口，暂时保留独立
     *
     * @param accountId
     *
     * @return
     *
     * @throws BusinessException
     */
    @Override
    public String createSeal(String accountId, String name, String typeface) throws BusinessException {
        // 印章模板类型,可选SQUARE-正方形印章 | RECTANGLE-矩形印章 | BORDERLESS-无框矩形印章
        PersonTemplateType personTemplateType = PersonTemplateType.HWXK;

        // 印章颜色：RED-红色 | BLUE-蓝色 | BLACK-黑色
        SealColor sealColor = SealColor.BLACK;
        //根据账号生成
        if(StringUtils.isNotBlank(accountId)){
            return sealHelper.addTemplateSeal(accountId, personTemplateType, sealColor);
        }
        //生成本地签章
        return sealHelper.addTemplateSeal(personTemplateType, name, sealColor);
    }

    @Override
    public void deleteAccount(String accountId) throws BusinessException {
        log.info("删除签名账号：accountId={}", accountId);
        accountHelper.deleteAccount(accountId);
    }

    @Override
    public PreSignResultVO preSign(SignFileParamVO signParamVO, SignAccountVO signAccountVO) throws BusinessException {
        //log.info("eqianbap preSign info : signParamVO={},signAccountVO={}", signParamVO, signAccountVO);
        PreSignResultVO resultVO = new PreSignResultVO();
        SignFaceResultVO faceResultVO;
        switch (signAccountVO.getRequestFrom()) {
            case SignAccountVO.REQUEST_FROM_CMS:
                faceResultVO = openApiHelper.getFaceUrl(signParamVO, signAccountVO);
                resultVO.setUniqueId(faceResultVO.getBizId());
                resultVO.setUrl(faceResultVO.getShortUrl());
                break;
            case SignAccountVO.REQUEST_FROM_PC:
            case SignAccountVO.REQUEST_FROM_APP:
                faceResultVO = openApiHelper.getFaceUrl(signParamVO, signAccountVO);
                resultVO.setUniqueId(faceResultVO.getBizId());
                resultVO.setUrl(faceResultVO.getShortUrl());
                break;
            case SignAccountVO.REQUEST_FROM_SYSTEM:
                resultVO.setUniqueId(UUID.randomUUID().toString());
            default:
                break;
        }
        return resultVO;
    }


    /**
     * <pre>
     *     意向签署方式：短信验证码
     * </pre>
     *
     * @param accountId
     *
     * @return
     *
     * @throws BusinessException
     */
    private String getSignCode(String accountId) throws BusinessException {
        mobileCodeHelper.sendSignMobileCode(accountId);
        return UUID.randomUUID().toString();
    }

    @Override
    public void sign(SignFileParamVO signParamVO, SignAccountVO signAccountVO, IEsignService callback) throws BusinessException {
        // 设置个人客户签署PDF文档信息
        //        SignPDFStreamBean signPDFStreamBean = getSignPDFStreamBean(signParamVO);
        SignPDFFileBean fileBean = this.getSignPDFFileBean(signParamVO);

        //设置签章位置信息
        PosBean posBean = this.getPostBean(signParamVO);
        //获取签章类型
        SignType signType = SignType.Key;
        //获取签章账号
        String accountId = signAccountVO.getOpenId();
        //个人签章数据
        String sealData = signAccountVO.getSealData();

        FileDigestSignResult signResult = null;

        if (signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_APP
            || signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_PC) {
            log.info("人脸识别认证签名");
            WillnessInfo willnessInfo = this.getWillnessInfo(signAccountVO);
            SigningAccount signingAccount = this.getSigningAccount(signAccountVO);
            List<SigningInfo> signinfos = this.getSigningInfos(fileBean, posBean, sealData, signType);
            signResult = signHelper.localSignWithWillness(willnessInfo, signingAccount, signinfos);
        } else if (signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_CMS) {
            log.info("人脸识别认证签名");
            WillnessInfo willnessInfo = this.getWillnessInfo(signAccountVO);
            SigningAccount signingAccount = this.getSigningAccount(signAccountVO);
            List<SigningInfo> signinfos = this.getSigningInfos(fileBean, posBean, sealData, signType);
            signResult = signHelper.localSignWithWillness(willnessInfo, signingAccount, signinfos);
        } else if (signAccountVO.getRequestFrom() == SignAccountVO.REQUEST_FROM_SYSTEM) {
            log.info("系统无意愿签署");
            signResult = signHelper.localAutoSignPDF(accountId, sealData, fileBean, posBean, signType);
        } else {
            log.info("未按指定验证方式传参");
            throw new BusinessException(ExceptionCodes.ESIGN_SIGN_NOT_MATCH);
        }
        //验签
        VerifyPdfResult verifyPdfResult = verifyPDFHelper.localVerifyPdf(signResult.getStream());
        //        //解析签署时间_每次取最后一个
        List<SignBean> signBeans = verifyPdfResult.getSignatures();
        String signDate = signBeans.get(signBeans.size() - 1).getSignature().getSignDate();
        log.info("签名时间：signDate={}", signDate);

        //回调签名结果
        callback.signResult(SignCallbackVO.builder()
            .openId(signAccountVO.getOpenId())
            .urId(signParamVO.getUrId())
            .signedPdfBase64(Base64.encodeBase64String(signResult.getStream()))
            .status(SignCallbackVO.STATUS_SIGNED)
            .uniqueId(signParamVO.getUnionId())
            .signTime(DateUtil.parseStrToDateTime(signDate))
            //                .signTime(new Date())
            .sealImage(signAccountVO.getSealImage())
            .build());

    }

    @Override
    public void resetSignPwd(String openId, String newPassword) {
        // 该厂商在预签署流程中从缓存读取上次设置的密码
    }

    /**
     * <pre>
     *     签署操作人账号信息
     * </pre>
     *
     * @param accountVO
     * @return
     */
    private WillnessInfo getWillnessInfo(SignAccountVO accountVO) {
        WillnessInfo willnessInfo = new WillnessInfo();
        //签署操作人完成的签署意愿认证的记录id
        willnessInfo.setBizId(accountVO.getBizId());
        //签署操作人账号
        willnessInfo.setWillnessAccountId(accountVO.getOpenId());
        log.info("签署操作人账号：willnessinfo={}", ToStringBuilder.reflectionToString(willnessInfo));
        return willnessInfo;
    }

    /**
     * <pre>
     *     签署主体账号信息
     * </pre>
     *
     * @return
     */
    private SigningAccount getSigningAccount(SignAccountVO accountVO) {
        SigningAccount signingAccount = new SigningAccount();
        //签署主体账号（可为个人/企业用户账号），将使用本账号绑定的默认数字证书进行签署。
        signingAccount.setSignAccountId(accountVO.getOpenId());
        log.info("签署主体账号信息：signingAccount={}", ToStringBuilder.reflectionToString(signingAccount));
        return signingAccount;
    }


    private List<SigningInfo> getSigningInfos(AbstractSignPdfBean streamBean, PosBean posBean, String sealData, SignType signType) {
        List<SigningInfo> list = new ArrayList<>();
        SigningInfo info = new SigningInfo();
        info.setFile(streamBean);

        List<PosBean> posBeans = new ArrayList<>();
        posBeans.add(posBean);
        info.setSignPos(posBeans);
        log.info("位置信息：pos={}", ToStringBuilder.reflectionToString(posBean));

        info.setSealData(sealData);
        info.setSignType(signType);

        list.add(info);
        return list;
    }


    private SignPDFStreamBean getSignPDFStreamBean(SignFileParamVO signParamVO) {
        SignPDFStreamBean signPDFFileBean = new SignPDFStreamBean();
        signPDFFileBean.setStream(Base64.decodeBase64(signParamVO.getPdfBase64()));
        return signPDFFileBean;
    }

    private SignPDFFileBean getSignPDFFileBean(SignFileParamVO signParamVO) {
        SignPDFFileBean fileBean = new SignPDFFileBean();
        fileBean.setSrcPdfFile(signParamVO.getPdfPath());
        //        File file = new File("/ehp/emr/test/test.pdf");
        //        if(!file.exists()){
        //            try {
        //                FileUtils.forceMkdir(file.getParentFile());
        //            } catch (IOException e) {
        //                e.printStackTrace();
        //            }
        //        }
        //        fileBean.setDstPdfFile("/ehp/emr/test/test.pdf");
        return fileBean;
    }

    private PosBean getPostBean(SignFileParamVO signParamVO) {
        PosBean posBean = new PosBean();
        posBean.setPosPage("1");
        //当前按关键字签名
        posBean.setKey(signParamVO.getKeyword());
        // 签署位置X坐标,默认值为0,以pdf页面的左下角作为原点,控制距离页面左端的横向移动距离,单位为px, 若为关键字定位，相对于关键字的X坐标偏移量
        posBean.setPosX(signParamVO.getX());
        // 签署位置Y坐标,默认值为0,以pdf页面的左下角作为原点,控制距离页面底端的纵向移动距离,单位为px, 若为关键字定位，相对于关键字的X坐标偏移量
        posBean.setPosY(signParamVO.getY());
        // 印章图片在PDF文件中的等比缩放大小,公章标准大小为4.2厘米即159px，宽度大于90px才能显示日期（当前版本不显示日期）
//        posBean.setWidth(90F);
        //显示签名时间
        posBean.setAddSignTime(signParamVO.getAddSignTime());
        return posBean;
    }

    /**
     * <pre>
     *     自定义签章类型与e签宝对应关系
     * </pre>
     *
     * @param signType
     *
     * @return
     */
    private SignType getSignType(Integer signType) {
        switch (signType) {
            case 1:
                return SignType.Single;
            case 2:
                return SignType.Multi;
            case 3:
                return SignType.Edges;
            case 4:
                return SignType.Key;
            default:
                return SignType.Key;
        }
    }


}
